// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024.
 * Description:add interfaces for hungtask_montior and add trace for mutex and semaphore
 * Author: langfei
 * Create: 2024-3-30
 */

#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/nmi.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/freezer.h>
#include <linux/kthread.h>
#include <linux/lockdep.h>
#include <linux/export.h>
#include <linux/sysctl.h>
#include <linux/suspend.h>
#include <linux/utsname.h>
#include <linux/sched/signal.h>
#include <linux/sched/debug.h>
#include <linux/sched/sysctl.h>
#include <trace/events/sched.h>

#include <linux/euleros_hungtask.h>
#include <linux/memblock.h>
#include <linux/kallsyms.h>
#include <linux/slab.h>

#ifdef CONFIG_EULEROS_HUNGTASK_DEBUG

DEFINE_SPINLOCK(hungtask_control_lock);
struct hungtask_control_ops *hungtask_control;
int hungtask_debug_enable __read_mostly;
struct hungtask_info *hungtask_lock;
char lock_name[KSYM_NAME_LEN];

void hungtask_debug_info(struct task_struct *t, unsigned long timeout)
{
	struct task_struct *p;
	struct hungtask_info *ht_info = NULL;
	struct hungtask_info *ht_info_curr = NULL;
	int i = 0;

	spin_lock(&hungtask_control_lock);
	if (hungtask_control && hungtask_control->add_hung_task)
		hungtask_control->add_hung_task(t, timeout);
	spin_unlock(&hungtask_control_lock);

	/* print debug info */
	if (!hungtask_debug_enable)
		return;

	ht_info_curr = &(hungtask_lock[t->pid]);
	if (ht_info_curr->locking_info.lock_addr == NULL)
		return;

	read_lock(&tasklist_lock);
	for_each_process(p) {
		ht_info = &(hungtask_lock[p->pid]);
		for (i = 0; i < HUNGTASK_LOCK_DEPTH; i++) {
			if (ht_info->locked_info[i].lock_addr == NULL)
				continue;
			if (ht_info_curr->locking_info.lock_addr ==
				ht_info->locked_info[i].lock_addr) {
				lookup_symbol_name((unsigned long)
					ht_info_curr->locking_info.lock_addr,
					lock_name);
				pr_err("%s fail to acquire the lock: %s",
					t->comm, lock_name);
				pr_err("owned by task: %s, pid: %d, called by: %pB\n",
					p->comm, p->pid,
					ht_info->locked_info[i].lock_caller);
			}
		}
	}
	read_unlock(&tasklist_lock);
}

/*
 * Register a dump function which used to dump memory information when OOM.
 */
void register_hungtask_control(struct hungtask_control_ops *co)
{
	spin_lock(&hungtask_control_lock);
	hungtask_control = co;
	spin_unlock(&hungtask_control_lock);
}
EXPORT_SYMBOL(register_hungtask_control);

void unregister_hungtask_control(struct hungtask_control_ops *co)
{
	spin_lock(&hungtask_control_lock);
	hungtask_control = NULL;
	spin_unlock(&hungtask_control_lock);
}
EXPORT_SYMBOL(unregister_hungtask_control);

void hungtask_lock_acquire(void *lock_addr, unsigned long *ip)
{
	struct hungtask_info *ht_info =  &(hungtask_lock[current->pid]);

	ht_info->locking_info.lock_addr = lock_addr;
	ht_info->locking_info.lock_caller = ip;
}

void __hungtask_lock_acquire_release(void *lock_addr, struct task_struct *p)
{
	struct hungtask_info *ht_info = &(hungtask_lock[p->pid]);

	if (ht_info->locking_info.lock_addr != lock_addr)
		return;

	ht_info->locking_info.lock_addr = NULL;
	ht_info->locking_info.lock_caller = NULL;
}

void hungtask_lock_acquire_release(void *lock_addr, struct task_struct *task)
{
	if (task)
		__hungtask_lock_acquire_release(lock_addr, task);
}

void hungtask_lock_acquired(void *lock_addr, unsigned long *ip)
{
	struct hungtask_info *ht_info = NULL;
	int i;

	hungtask_lock_acquire_release(lock_addr, current);
	ht_info = &(hungtask_lock[current->pid]);
	if (ht_info->locked_cnt >= HUNGTASK_LOCK_DEPTH)
		return;
	for (i = 0; i < HUNGTASK_LOCK_DEPTH; i++) {
		if (ht_info->locked_info[i].lock_addr == NULL) {
			ht_info->locked_info[i].lock_addr = lock_addr;
			ht_info->locked_info[i].lock_caller = ip;
			ht_info->locked_cnt++;
			return;
		}
	}
}

void hungtask_lock_acquired_release(void *lock_addr)
{
	struct hungtask_info *ht_info = NULL;
	int i = 0;

	ht_info = &(hungtask_lock[current->pid]);
	for (i = 0; i < HUNGTASK_LOCK_DEPTH; i++) {
		if (ht_info->locked_info[i].lock_addr == lock_addr) {
			ht_info->locked_info[i].lock_caller = NULL;
			ht_info->locked_info[i].lock_addr = NULL;
			ht_info->locked_cnt--;
			return;
		}
	}
}

static int __init hungtask_debug_setup(char *str)
{
	phys_addr_t size = sizeof(struct hungtask_info) * PID_MAX_LIMIT;

	if (hungtask_lock != NULL)
		return 0;

	if (strcmp(str, "on"))
		return 1;
	pr_info("start to enable hungtask debug.\n");
	hungtask_lock = memblock_alloc_from(size, SMP_CACHE_BYTES, 0);
	if (hungtask_lock == NULL) {
		pr_err("hungtask: alloc for hungtask_lock failed.\n");
		return 1;
	}
	memset(hungtask_lock, 0, sizeof(struct hungtask_info) * PID_MAX_LIMIT);
	hungtask_debug_enable = 1;
	return 0;
}
__setup("hungtask_debug=", hungtask_debug_setup);
#endif

